//------------------------------------------------------------------------------
// <copyright file="AsyncResultNoResult.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// <summary>
// Implementation of IAsyncResult with no return value.  Specifically, the operation either succeeds or fails
// Taken from Jeffrey Richter's Concurrent Affairs MSDN Library article
// with only stylistic changes
// </summary>
//------------------------------------------------------------------------------

namespace Microsoft.Internal.GamesTest.Rpc.Client
{
    using System;
    using System.Diagnostics;
    using System.Globalization;
    using System.Threading;

    /// <summary>
    /// Implementation of IAsyncResult with no return value.  Specifically, the operation either succeeds or fails.
    /// </summary>
    internal class AsyncResultNoResult : DisposableObject
    {
        // Field set at construction which do change after operation completes
        private const int StatePending = 0;
        private const int StateCompletedSynchronously = 1;
        private const int StateCompletedAsynchronously = 2;
        private int completedState;
        private string lastSetStackTrace;

        // Field that may or may not get set depending on usage
        private ManualResetEvent asyncWaitHandle;

        // Fields set when operation completes
        private Exception storedException;

        /// <summary>
        /// Initializes a new instance of the AsyncResultNoResult class.
        /// </summary>
        public AsyncResultNoResult()
        {
        }

        /// <summary>
        /// Gets a value indicating whether the asynchronous operation completed synchronously.
        /// </summary>
        public bool CompletedSynchronously
        {
            get { return this.completedState == StateCompletedSynchronously; }
        }

        /// <summary>
        /// Gets a WaitHandle that is used to wait for an asynchronous operation to complete.
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "The object is disposed correctly.")]
        public WaitHandle AsyncWaitHandle
        {
            get
            {
                if (this.asyncWaitHandle == null)
                {
                    bool done = this.IsCompleted;

                    ManualResetEvent mre = new ManualResetEvent(done);
                    if (Interlocked.CompareExchange(ref this.asyncWaitHandle, mre, null) != null)
                    {
                        // Another thread created this object's event; dispose the event we just created
                        mre.Dispose();
                        mre = null;
                    }
                    else
                    {
                        if (!done && this.IsCompleted)
                        {
                            // If the operation wasn't done when we created 
                            // the event but now it is done, set the event
                            this.asyncWaitHandle.Set();
                        }
                    }
                }

                return this.asyncWaitHandle;
            }
        }

        /// <summary>
        /// Gets a value indicating whether the asynchronous operation has completed.
        /// </summary>
        public bool IsCompleted
        {
            get { return this.completedState != StatePending; }
        }

        /// <summary>
        /// Tells the AsyncResultNoResult that the operation is completed.
        /// </summary>
        /// <param name="exception">Null if no exception occured, otherwise an exception representing the error that occured.</param>
        /// <param name="completedSynchronously">Whether or not the result compeleted Synchronously or Asynchronously.</param>
        public void SetAsCompleted(Exception exception, bool completedSynchronously)
        {
            // Passing null for exception means no error occurred; this is the common case
            this.storedException = exception;

            // The completedState field MUST be set prior calling the callback
            int prevState = Interlocked.Exchange(ref this.completedState, completedSynchronously ? StateCompletedSynchronously : StateCompletedAsynchronously);
            if (prevState != StatePending)
            {
                throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "You can set a result only once. Last stack trace:{0}{1}", Environment.NewLine, this.lastSetStackTrace));
            }
            else
            {
                this.lastSetStackTrace = Environment.StackTrace;
            }

            // If the event exists, set it
            if (this.asyncWaitHandle != null)
            {
                this.asyncWaitHandle.Set();
            }
        }

        /// <summary>
        /// Waits for the operation to complete and throws an exception if there was an error.
        /// </summary>
        /// <param name="timeout">The amount of time to wait for the async operation to complete.</param>
        public void EndInvoke(TimeSpan timeout)
        {
            // This method assumes that only 1 thread calls EndInvoke for this object
            if (!this.IsCompleted)
            {
                int waitIndex = WaitHandle.WaitAny(new WaitHandle[] { this.AsyncWaitHandle }, timeout, false);

                // Always clean up the async handle
                this.AsyncWaitHandle.Close();
                this.asyncWaitHandle = null;  // Allow early GC

                if (waitIndex == WaitHandle.WaitTimeout)
                {
                    throw new TimeoutException("Time Out Exception: The requested asynchronous operation took too long to complete.");
                }
            }

            // Operation is done: if an exception occured, throw it
            if (this.storedException != null)
            {
                throw this.storedException;
            }
        }

        /// <summary>
        /// Dispose of any managed resources owned by this object.
        /// </summary>
        protected override void DisposeManagedResources()
        {
            if (this.asyncWaitHandle != null)
            {
                this.asyncWaitHandle.Dispose();
            }

            base.DisposeManagedResources();
        }
    }
}